﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Taxonomy;

namespace SharePointHelper.DLL.SSOM
{
	public class Taxonomy
	{
		/// <summary>
		/// Special unicode ampersand that SharePoint uses for term names instead of regular ampersands
		/// </summary>
		public const string AmpersandFullwidth = "\uFF06";

		/// <summary>
		/// returns the internal name of the hidden note field that accompanyies a managed metadata site column
		/// </summary>
		/// <param name="managedMetadataSiteColumnInternalName"></param>
		/// <returns></returns>
		public static string FormatNoteFieldInternalName(string managedMetadataSiteColumnInternalName)
		{
			if (!string.IsNullOrEmpty(managedMetadataSiteColumnInternalName))
			{
				return managedMetadataSiteColumnInternalName + "TaxHTField0";
			}
			else { return managedMetadataSiteColumnInternalName; }
		}

		/// <summary>
		/// Get the Term Store that stores all the terms
		/// Main ones are "Keywords", "Managed Metadata Service Application", etc.
		/// </summary>
		/// <param name="site"></param>
		/// <param name="name"></param>
		/// <returns></returns>
		public static TermStore GetTermStore(SPSite site, string name)
		{
			TermStore termStore = null;
			try
			{
				// init
				if (site == null) { throw new Exception("Failed to pass SPSite"); }
				if (string.IsNullOrEmpty(name)) { throw new Exception("Term store name passed is null"); }
				name = name.ToLower().Trim();

				// get term store session
				TaxonomySession session = new TaxonomySession(site);
				if (session == null || session.TermStores == null || session.TermStores.Count == 0)
				{ throw new Exception("No term stores found for site collection: " + site.Url); }

				foreach (TermStore _termStore in session.TermStores)
				{
					if (_termStore.Name.ToLower().Trim() == name) { termStore = _termStore; break; }
				}

				if (termStore == null) { throw new Exception("Failed to find term store: " + name + " for site: " + site.Url); }
			}
			catch (Exception ex)
			{
				Log.WriteError("rtutdr7idurdyrsdy", ex, "term store name: " + name);
				termStore = null;
			}
			return termStore;
		}

		/// <summary>
		/// Get the default term store for passed site
		/// Most likely, "Managed Metadata Service Application"
		/// </summary>
		/// <returns></returns>
		public static TermStore GetTermStore(SPSite site)
		{
			TermStore termStore = null;
			try
			{
				// init
				if (site == null) { site = Site.GetSite(); }
				if (site == null) { throw new Exception("Failed to get current SPSite"); }

				// get term store session
				TaxonomySession session = new TaxonomySession(site);
				if (session == null || session.TermStores == null || session.TermStores.Count == 0)
				{ throw new Exception("No term stores found for site collection: " + site.Url); }

				// return default term store
				termStore = session.TermStores[0];
			}
			catch (Exception ex)
			{
				Log.WriteError("5ysdtrdfdsfsdf", ex);
				termStore = null;
			}
			return termStore;
		}

		/// <summary>
		/// Get the default term store for current site
		/// Most likely, "Managed Metadata Service Application"
		/// </summary>
		/// <returns></returns>
		public static TermStore GetTermStore()
		{
			TermStore termStore = null;
			try
			{
				return GetTermStore(null);
			}
			catch (Exception ex)
			{
				Log.WriteError("r6eufddxfdfsdr65", ex);
				termStore = null;
			}
			return termStore;
		}

		/// <summary>
		/// get term store by id:
		/// TaxonomyField.SspId
		/// http://stackoverflow.com/questions/2745769/programmatically-set-a-taxonomyfield-on-a-list-item
		/// </summary>
		/// <param name="site"></param>
		/// <param name="termStoreId"></param>
		/// <returns></returns>
		public static TermStore GetTermStore(SPSite site, Guid termStoreId)
		{
			TermStore termStore = null;
			try
			{
				// init
				if (site == null) { throw new Exception("Failed to pass SPSite"); }
				if (termStoreId == null || termStoreId == Guid.Empty) { throw new Exception("TermStore Id passed is invalid"); }

				TaxonomySession session = new TaxonomySession(site); 
				termStore = session.TermStores[termStoreId];

				if (termStore == null) { throw new Exception("Failed to find term store using id: " + termStoreId + " for site: " + site.Url); }
			}
			catch (Exception ex)
			{
				Log.WriteError("w6uetdiddfdsf", ex);
				termStore = null;
			}
			return termStore;
		}

		public static TermStore GetEnterpriseKeywordsTermStore(SPSite site)
		{
			TermStore store = null;
			try
			{
				// init
				if (site == null) { site = Site.GetSite(); }
				if (site == null) { throw new Exception("SPSite passed is n ull and cannot determine current SPSite"); }


				// get session
				TaxonomySession session = new TaxonomySession(site);
				if (session == null || session.TermStores == null || session.TermStores.Count == 0)
				{ throw new Exception("No term stores found for site collection: " + site.Url); }

				// get keywords termstore
				store = session.DefaultKeywordsTermStore;
			}
			catch (Exception ex)
			{
				Log.WriteError("esrydjgfdxd5r", ex);
				store = null;
			}
			return store;
		}


		/// <summary>
		/// Get term store group
		/// </summary>
		/// <param name="termStore"></param>
		/// <param name="name"></param>
		/// <returns></returns>
		public static Group GetTermGroup(TermStore termStore, string name)
		{
			Group group = null;
			try
			{
				// init
				if (termStore == null) { throw new Exception("Term Store passed is null"); }
				if (string.IsNullOrEmpty(name)) { throw new Exception("Group name passed is null"); }
				name = name.Trim().ToLower();


				foreach (Group _group in termStore.Groups)
				{
					if (_group.Name.ToLower().Trim() == name) { group = _group; }
				}
				if (group == null) { throw new Exception("Failed to find term group: " + name + " in term store: " + termStore.Name); }
			}
			catch (Exception ex)
			{
				Log.WriteError("u5e75oie6Lrhersyresre65", ex);
				group = null;
			}
			return group;
		}

		public static TermSet GetTermSet(Group group, string name)
		{
			TermSet termSet = null;
			try
			{
				// init
				if (group == null) { throw new Exception("Term Group passed is null"); }
				if (string.IsNullOrEmpty(name)) { throw new Exception("Term Set name passed is null"); }
				name = name.Trim().ToLower();


				foreach (TermSet _termSet in group.TermSets)
				{
					if (_termSet.Name.ToLower().Trim() == name) { termSet = _termSet; }
				}
				if (termSet == null) { throw new Exception("Failed to find term set: " + name + " in term group: " + group.Name); }
			}
			catch (Exception ex)
			{
				Log.WriteError("68ottytrdtrdrtdtr", ex);
				termSet = null;
			}
			return termSet;
		}

		/// <summary>
		/// Get term set by using termSetId:
		/// TaxonomyField.TermSetId
		/// http://stackoverflow.com/questions/2745769/programmatically-set-a-taxonomyfield-on-a-list-item
		/// </summary>
		/// <param name="termStore"></param>
		/// <param name="termSetId"></param>
		/// <returns></returns>
		public static TermSet GetTermSet(TermStore termStore, Guid termSetId)
		{
			TermSet termSet = null;
			try
			{
				// init
				if (termStore == null) { throw new Exception("Term Store passed is null"); }
				if (termSetId == null || termSetId == Guid.Empty) { throw new Exception("Term Set Id passed is invalid"); }

				termSet = termStore.GetTermSet(termSetId); 
				if (termSet == null) { throw new Exception("Failed to find term set using Id: " + termSetId + " in term store: " + termStore.Name); }
			}
			catch (Exception ex)
			{
				Log.WriteError("68ottytrdtrdrtdtr", ex);
				termSet = null;
			}
			return termSet;
		}

		/// <summary>
		/// Get term set from the default term store
		/// WARNING: This returns the first term set with a matching name. 
		/// There might be more than one term set under different groups w/ the same name
		/// </summary>
		/// <param name="termSetName"></param>
		/// <returns></returns>
		public static TermSet GetTermSet(string termSetName)
		{
			TermSet termSet = null;
			try
			{
				// init
				if (string.IsNullOrEmpty(termSetName)) { throw new Exception("TermSet name passed is null"); }
				termSetName = termSetName.Trim().ToLower();


				// get term store
				TermStore termStore = GetTermStore();
				if (termStore == null) { throw new Exception("Failed to get current site's default term store"); }

				// loop through the groups
				foreach (Group group in termStore.Groups)
				{
					// loop through it's termsets to find a match
					foreach (TermSet _termSet in group.TermSets)
					{
						if (_termSet.Name.Trim().ToLower() == termSetName)
						{
							// match found!
							termSet = _termSet;
							break;
						}
					}
				}
				if (termSet == null) { throw new Exception("Failed to find termset in default termstore: " + termStore.Name); }
			}
			catch (Exception ex)
			{
				Log.WriteError("er5yerdfsds", ex, "term set name: " + termSetName);
				termSet = null;
			}
			return termSet;
		}

		///// <summary>
		///// Gets termset under the passed term group
		///// for passed site
		///// </summary>
		///// <param name="termGroupName"></param>
		///// <param name="termSetName"></param>
		///// <returns></returns>
		//public static TermSet GetTermSet(SPSite site, string termGroupName, string termSetName)
		//{
		//    TermSet termSet = null;
		//    try
		//    {
		//        // init
		//        if (string.IsNullOrEmpty(termGroupName)) { throw new Exception("Term Group name passed is null"); }
		//        if (string.IsNullOrEmpty(termSetName)) { throw new Exception("TermSet name passed is null"); }
		//        termSetName = termSetName.Trim().ToLower();


		//        // get term store
		//        TermStore termStore = GetTermStore(site);
		//        if (termStore == null) { throw new Exception("Failed to get current site's default term store"); }

		//        // get term group
		//        Group termGroup = GetTermGroup(termStore, termGroupName);
		//        if (termGroup == null) { throw new Exception("Failed to get term group using name: " + termGroupName); }

		//        // get it's term set
		//        foreach (TermSet _termSet in termGroup.TermSets)
		//        {
		//            if (_termSet.Name.Trim().ToLower() == termSetName)
		//            {
		//                // match found!
		//                termSet = _termSet;
		//                break;
		//            }
		//        }
		//        if (termSet == null) { throw new Exception("Failed to find termset: " + termSetName + " in term group: " + termGroupName); }
		//    }
		//    catch (Exception ex)
		//    {
		//        Log.WriteError("5urthfgdsfdr", ex, "term set: " + termSetName + " term group: " + termGroupName);
		//        termSet = null;
		//    }
		//    return termSet;
		//}

		/// <summary>
		/// Get keywords termset
		/// LARGE!
		/// </summary>
		/// <param name="termStore"></param>
		/// <returns></returns>
		public static TermSet GetEnterpriseKeywordsTermSet(SPSite site)
		{
			TermSet keywords = null;
			try
			{
				// init
				if (site == null) { site = Site.GetSite(); }
				if (site == null) { throw new Exception("No SPSite passed and failed to get the current site"); }

				// get store
				TermStore store = GetEnterpriseKeywordsTermStore(site);

				// get term set
				keywords = store.SystemGroup.TermSets["Keywords"];
			}
			catch (Exception ex)
			{
				Log.WriteError("re6udfdfdsf", ex);
				keywords = null;
			}
			return keywords;
		}

		/// <summary>
		/// get all the keywords termset for current site
		/// LARGE!!
		/// </summary>
		/// <returns></returns>
		public static TermSet GetEnterpriseKeywordsTermSet()
		{
			TermSet keywords = null;
			try
			{
				// call original
				return GetEnterpriseKeywordsTermSet(null);
			}
			catch (Exception ex)
			{
				Log.WriteError("e5ye5sysye5esrdsd4", ex);
				keywords = null;
			}
			return keywords;
		}

		/// <summary>
		/// Get Keyword terms
		/// HUGE!
		/// </summary>
		/// <param name="site"></param>
		/// <param name="maxNumberOfKeywords">get only a set number of keywords. Null or 0 = get all terms</param>
		/// <returns></returns>
		public static TermCollection GetEnterpriseKeywordTerms(SPSite site, int? maxNumberOfKeywords)
		{
			TermCollection terms = null;
			try
			{
				// init
				if (site == null) { site = Site.GetSite(); }
				if (site == null) { throw new Exception("No SPSite passed and failed to get the current site"); }


				// get keywords termset
				TermSet set = GetEnterpriseKeywordsTermSet(site);

				// get keyword terms
				if (maxNumberOfKeywords == null || maxNumberOfKeywords == 0) 
				{ 
					terms = set.GetAllTerms(); 
				}
				else
				{
					terms = set.GetTerms(maxNumberOfKeywords.Value);
				}
			}
			catch (Exception ex)
			{
				Log.WriteError("tudthfdsdsrsxed", ex);
				terms = null;
			}
			return terms;
		}

		/// <summary>
		/// Get a term set for the current site, using passed group name and then set name
		/// </summary>
		/// <param name="termGroupName"></param>
		/// <param name="termSetName"></param>
		/// <returns></returns>
		public static TermSet GetTermSet(string termGroupName, string termSetName)
		{
			TermSet termSet = null;
			try
			{
				return GetTermSet(null, termGroupName, termSetName);
			}
			catch (Exception ex)
			{
				Log.WriteError("erydrddsrs5r4", ex, "term group: " + termGroupName + " term set: " + termSetName);
				termSet = null;
			}
			return termSet;
		}

		/// <summary>
		/// Get term set using passed term store, term group name, and set name
		/// </summary>
		/// <param name="termStore"></param>
		/// <param name="termGroupName"></param>
		/// <param name="termSetName"></param>
		/// <returns></returns>
		public static TermSet GetTermSet(TermStore termStore, string termGroupName, string termSetName)
		{
			TermSet termSet = null;
			try
			{
				// init
				if (termStore == null) { termStore = GetTermStore(); }
				if (termStore == null) { throw new Exception("Term Store passed is null and failed to get from current context"); }
				if (string.IsNullOrEmpty(termGroupName)) { throw new Exception("Term Group name passed is null"); }
				if (string.IsNullOrEmpty(termSetName)) { throw new Exception("Term Set name passed is null"); }


				// get group
				Group termGroup = GetTermGroup(termStore, termGroupName);
				if (termGroup == null) { throw new Exception("Failed to get term group using group name: " + termGroupName); }


				// get term set (overload)
				termSet = GetTermSet(termGroup, termSetName);
				if (termSet == null) { throw new Exception("Failed to get term set under group: " + termGroupName + " with set name: " + termSetName); }

			}
			catch (Exception ex)
			{
				Log.WriteError("redfzGGdszsdzds", ex, "term group: " + termGroupName + " term set: " + termSetName);
				termSet = null;
			}
			return termSet;
		}

		/// <summary>
		/// Get a term by passed name
		/// IMPORTANT! This is not the most accurate as there can be duplicate terms in a term hierarchy
		/// Matches term name and term labels
		/// </summary>
		/// <param name="name"></param>
		/// <param name="termSet"></param>
		/// <returns></returns>
		public static Term GetTerm(string name, TermSet termSet)
		{
			Term term = null;
			try
			{
				// init
				if (termSet == null) { throw new Exception("Term Set passed is null"); }
				if (string.IsNullOrEmpty(name)) { throw new Exception("Term name passed is null"); }
				name = name.Trim().ToLower();

				// get all terms into flat list
				var terms = termSet.GetAllTerms();

				// find term
				term = FindTerm(name, terms, true);
			}
			catch (Exception ex)
			{
				Log.WriteError("76iedydfdrstser5r6trfgc", ex);
				term = null;
			}
			return term;
		}

		/// <summary>
		/// Find term by termId guid:
		/// </summary>
		/// <param name="termStore"></param>
		/// <param name="termId"></param>
		/// <returns></returns>
		public static Term GetTerm(Guid termId, TermStore termStore)
		{
			Term term = null;
			try
			{
				// init
				if (termStore == null) { throw new Exception("Term Store passed is null"); }
				if (termId == null || termId == Guid.Empty) { throw new Exception("TermId passed is invalid"); }

				term = termStore.GetTerm(termId);
			}
			catch (Exception ex)
			{
				Log.WriteError("re6iutyerswJJsdrt", ex);
				term = null;
			}
			return term;
		}

		public static TermCollection GetTerms(Guid[] termIds, TermStore termStore)
		{
			TermCollection terms = null;
			try
			{
				// init
				if (termStore == null) { throw new Exception("Term Store passed is null"); }
				if (termIds == null || termIds.Length == 0) { throw new Exception("TermIds array passed is invalid"); }

				// get terms
				terms = termStore.GetTerms(termIds);
			}
			catch (Exception ex)
			{
				Log.WriteError("ur6fgdfsestsert6", ex);
				terms = null;
			}
			return terms;
		}


		/// <summary>
		/// Get terms collection for a specific termset
		/// IMPORTANT: You must set max number of terms to return, cannot be zero!
		/// </summary>
		/// <param name="termStore"></param>
		/// <param name="termGroupName"></param>
		/// <param name="termSetName"></param>
		/// <param name="maxTermsToReturn">Cannot be zero!</param>
		/// <returns></returns>
		public static TermCollection GetTerms(TermStore termStore, string termGroupName, string termSetName)
		{
			TermCollection terms = null;
			try
			{
				// init
				if (termStore == null) { throw new Exception("Term Store passed is null"); }
				if (string.IsNullOrEmpty(termGroupName)) { throw new Exception("Term group name passed is invalid"); }
				if (string.IsNullOrEmpty(termSetName)) { throw new Exception("Term set name passed is invalid"); }


				// get termset
				TermSet termSet = GetTermSet(termStore, termGroupName, termSetName);
				if (termSet == null) { throw new Exception("Failed to get term set"); }

				// get terms collection for termset
				terms = termSet.GetAllTerms();
			}
			catch (Exception ex)
			{
				Log.WriteError("eys5dhstrdfxrtse5", ex, "groupname: "+ termGroupName + " termSet: " + termSetName);
				terms = null;
			}
			return terms;
		}


		/// <summary>
		/// get all terms recursively walking down the hierarchy
		/// </summary>
		/// <param name="parentTerms"></param>
		/// <param name="isCustomSortOrder"></param>
		/// <param name="maxLevelsDeep">Defines how far down the hierarchy to get descendant terms</param>
		/// <returns></returns>
		private static List<Term> getDescendants(Term parentTerm, bool isCustomSortOrder, int maxLevelsDeep,
			ref int currentLevel)
		{
			var descendantsTerms = new List<Term>();
			try
			{
				// init
				if (parentTerm == null) { throw new Exception("Parent term passed is null"); }
				if (maxLevelsDeep < 1) { maxLevelsDeep = 1000; }


				if ((currentLevel + 1) <= maxLevelsDeep)
				{
					// increment level
					currentLevel++;

					// get child terms (custom sorted if needed)
					var customSortOrder = string.Empty;
					if (isCustomSortOrder) { customSortOrder = parentTerm.CustomSortOrder; }
					var childTerms = Taxonomy.CustomSortOrderTerms(parentTerm.Terms, customSortOrder);

					foreach (var childTerm in childTerms)
					{
						// add child term to collection
						descendantsTerms.Add(childTerm);

						// get grandchild terms?
						var grandchildrenTerms = getDescendants(childTerm, isCustomSortOrder, maxLevelsDeep, ref currentLevel);
						if (grandchildrenTerms != null && grandchildrenTerms.Count > 0)
						{
							descendantsTerms.AddRange(grandchildrenTerms);
						}
					}

					// decrement level
					currentLevel--;

				} // if level <= max level deep
			}
			catch (Exception ex)
			{
				Log.WriteError("rtyugydsGGtdsdtdstt", ex);
				descendantsTerms = null;
			}
			return descendantsTerms;
		}

		/// <summary>
		/// Returns descendant terms to the passed term: 
		/// custom sort and max levels deep can be defined
		/// </summary>
		/// <param name="term"></param>
		/// <param name="isCustomSortOrder"></param>
		/// <param name="maxLevelsDeep">Defines how far down the hierarchy to get descendant terms</param>
		/// <returns></returns>
		public static List<Term> GetDescendants(Term term, bool isCustomSortOrder, int maxLevelsDeep)
		{
			if (term == null) { Log.WriteError("i9hu8fyt76drsdzfxfdzdxzf", "Term passed is null"); return null; }
			else
			{
				var currentLevel = TermLevel(term.Id, term.TermSet);
				return getDescendants(term, isCustomSortOrder, maxLevelsDeep, ref currentLevel);
			}
		}

		/// <summary>
		/// Gets all terms from termset:
		/// custom sort and max levels deep can be defined
		/// </summary>
		/// <param name="termSet"></param>
		/// <param name="isCustomSortOrder"></param>
		/// <param name="maxLevelsDeep">Defines how far down the hierarchy to get descendant terms</param>
		/// <returns></returns>
		public static List<Term> GetTerms(TermSet termSet, bool isCustomSortOrder, int maxLevelsDeep)
		{
			if (termSet == null) { Log.WriteError("9054etesrwrsesdrgdfg", "TermSet passed is null"); return null; }
			else
			{
				var terms = new List<Term>();

				// get root terms
				var customSortOrder = string.Empty;
				if (isCustomSortOrder) { customSortOrder = termSet.CustomSortOrder; }
				var rootTerms = Taxonomy.CustomSortOrderTerms(termSet.Terms, customSortOrder);
				var currentLevel = 1;

				foreach (var rootTerm in rootTerms)
				{
					// add root term
					terms.Add(rootTerm);

					// add descendant terms
					var descendants = getDescendants(rootTerm, isCustomSortOrder, maxLevelsDeep, ref currentLevel);
					if (descendants != null && descendants.Count > 0) { terms.AddRange(descendants); }
				}
				return terms;
			}
		}


		/// <summary>
		/// Create a new term, with option to save
		/// If isSave == false, then manually TermStore.CommitAll()
		/// http://msdn.microsoft.com/en-us/library/ee578696%28v=office.14%29.aspx
		/// </summary>
		/// <param name="termSet"></param>
		/// <param name="name"></param>
		/// <param name="parentTerm">If defined, then will save the term as a child of the parent term passed</param>
		/// <param name="isSave">If true, will save the term to term set</param>
		/// <param name="isSkipSecurityCheck">If true, then will allow anyone to add the term</param>
		/// <returns></returns>
		public static Term CreateTerm(TermSet termSet, Term parentTerm, string name, bool isSave, 
			bool isSkipSecurityCheck)
		{
			Term term = null;
			bool isCloseTermSetForCreation = false;
			string originalName = name;
			try
			{
				if (string.IsNullOrEmpty(name)) { throw new Exception("Term name not passed"); }
				if (termSet == null && parentTerm != null) { termSet = parentTerm.TermSet; }
				if (termSet == null) { throw new Exception("TermSet passed is null"); }
				

				// format termname
				name = FormatTermName(name);

				// open termset for adding terms
				if (isSave && isSkipSecurityCheck && !termSet.IsOpenForTermCreation)
				{
					// Log.WriteInformation("Opening TermSet for adding terms. You must manually close it again. TermSet: " + termSet.Name);
					termSet.IsOpenForTermCreation = true;
					termSet.TermStore.CommitAll();
					isCloseTermSetForCreation = true;
				}

				// dup check
				//if (isDupCheck)
				//{
				//    bool isDup = false;
				//    if (parentTerm != null)
				//    {
				//        foreach (Term t in parentTerm.Terms) { if (t.Name == name) { isDup = true; break; } }
				//    }
				//    else
				//    {
				//        foreach (Term t in termSet.Terms) { if (t.Name == name) { isDup = true; break; } }
				//    }
				//    if (isDup) { throw new Exception("Duplicate term already exists using name: " + name); }
				//}

				// create term
				if (parentTerm == null) { term = termSet.CreateTerm(name, 1033); }
				else { term = parentTerm.CreateTerm(name, 1033); }
				if (term == null) { throw new Exception("Failed to create term with unknown error"); }

				// save
				if (isSave) { termSet.TermStore.CommitAll(); }
			}
			catch (Exception ex)
			{
				Log.WriteError("tsytdryidtdhtdrtdyrdyt", ex, "term formatted name: " + name + ", original name: "
					+ originalName);
				term = null;
			}
			finally
			{
				if (termSet != null && isCloseTermSetForCreation)
				{
					// close term set for allowing anyone to add terms, if needed
					termSet.IsOpenForTermCreation = false;
				}
			}
			return term;
		}

		/// <summary>
		/// Create a new term as a root term in the passed term set
		/// If isSave == false, then manually TermStore.CommitAll()
		/// </summary>
		/// <param name="termSet"></param>
		/// <param name="name"></param>
		/// <param name="isSave"></param>
		/// <returns></returns>
		public static Term CreateTerm(TermSet termSet, string name, bool isSave)
		{
			return CreateTerm(termSet, null, name, isSave, true); 
		}

		/// <summary>
		/// Create a new term as a child term of the passed parent term
		/// If isSave == false, then manually TermStore.CommitAll()
		/// </summary>
		/// <param name="parentTerm"></param>
		/// <param name="name"></param>
		/// <param name="isSave"></param>
		/// <returns></returns>
		public static Term CreateTerm(Term parentTerm, string name, bool isSave)
		{
			return CreateTerm(null, parentTerm, name, isSave, true);
		}

		/// <summary>
		/// 
		/// http://www.ride-the-bytes.com/?p=782
		/// </summary>
		public static System.Web.UI.WebControls.TreeView GetTaxonomyTree(Group rootTermGroup, TermSet rootTermSet, string imageUrl)
		{
			System.Web.UI.WebControls.TreeView tree = null;
			try
			{
				// init
				if (rootTermGroup == null && rootTermSet == null) { throw new Exception("Root node not defined"); }
				//if ((rootTermStore != null && (rootTermGroup !=null || rootTermSet != null)) || 
				//    (rootTermGroup != null && (rootTermStore != null || rootTermSet != null))
				//    )
				//{
				//    throw new Exception("Only one root node can be defined"); 
				//}
				//tree = new System.Web.UI.WebControls.TreeView();


				//if (rootTermStore != null)
				//{
				//    // create root node
				//    tree.Nodes.Add(new System.Web.UI.WebControls.TreeNode(rootTermStore.Name, rootTermStore.Id.ToString(), imageUrl));

				//    // get child group nodes
				//    foreach (Group group in rootTermStore.Groups)
				//    {
				//        tree.Nodes[0].ChildNodes.Add(
				//            new System.Web.UI.WebControls.TreeNode(group.Name, group.Id.ToString(), imageUrl));
				//    }
				//}

			}
			catch (Exception ex)
			{
				Log.WriteError("stryftuofddfxdrers6", ex);
				tree = null;
			}
			return tree;
		}

		private void getChildTerms(System.Web.UI.WebControls.TreeNode currentNode, Term term, string imageUrl)
		{
			foreach (Term subTerm in term.Terms)
			{
				System.Web.UI.WebControls.TreeNode subNode = 
					new System.Web.UI.WebControls.TreeNode(subTerm.Name, subTerm.Id.ToString(), imageUrl);
				currentNode.ChildNodes.Add(subNode);
				getChildTerms(subNode, subTerm, imageUrl);
			}
		}

		

		/// <summary>
		/// Create new terms
		/// </summary>
		/// <param name="termSet"></param>
		/// <param name="parentTerm"></param>
		/// <param name="termNameAndLabelsToCreate">string = name, string[] = lables</param>
		/// <param name="isSave"></param>
		/// <returns></returns>
		public static List<Term> CreateTerms(TermSet termSet, Term parentTerm, 
			Dictionary<string, string[]> termNameAndLabelsToCreate, bool isSave)
		{
			List<Term> terms = null;
			try
			{
				// init
				if (termSet == null && parentTerm == null) { throw new Exception("TermSet and parent term cannot both be null"); }
				if (termNameAndLabelsToCreate == null || termNameAndLabelsToCreate.Count == 0) { throw new Exception("Draft Terms collection passed is null or empty"); }
				terms = new List<Term>();


				// get term store
				TermStore termStore = null;
				if (termSet != null) { termStore = termSet.TermStore; } else { termStore = parentTerm.TermStore; }
				if (termStore == null) { throw new Exception("Failed to get term store object from passed termset or term"); }


				// add child terms to parent term passed
				foreach (KeyValuePair<string, string[]> t in termNameAndLabelsToCreate)
				{
					// a.) create term
					Term newTerm = null;
					if (parentTerm != null)
					{
						newTerm = parentTerm.CreateTerm(t.Key, 1033);
					}
					else
					{
						newTerm = termSet.CreateTerm(t.Key, 1033);
					}

					// b.) add labels
					if (t.Value != null && t.Value.Length > 0)
					{
						foreach (string label in t.Value) { newTerm.CreateLabel(label, 1033, false); }
					}

					// c.) add term to collection
					terms.Add(newTerm);
				}

				// save?
				if (isSave) { termStore.CommitAll(); }
			}
			catch (Exception ex)
			{
				Log.WriteError("6rurtsreaasres6rd", ex);
				terms = null;
			}
			return terms;
		}

		/// <summary>
		/// Create many root terms in passed term set
		/// </summary>
		/// <param name="termSet"></param>
		/// <param name="termsToCreate"></param>
		/// <returns></returns>
		public static List<Term> CreateTerms(TermSet termSet, string[] termsToCreate)
		{
			// convert terms array into dictionary
			Dictionary<string, string[]> termsToCreateDictionary = new Dictionary<string,string[]>();
			if (termsToCreate != null && termsToCreate.Length > 0)
			{
				foreach (string t in termsToCreate) { termsToCreateDictionary.Add(t, null); }
			}
			return CreateTerms(termSet, null, termsToCreateDictionary, true);
		}

		/// <summary>
		/// create many child terms for the passed parent term
		/// </summary>
		/// <param name="parentTerm"></param>
		/// <param name="termsToCreate"></param>
		/// <returns></returns>
		public static List<Term> CreateTerms(Term parentTerm, string[] termsToCreate)
		{
			// convert terms array into dictionary
			Dictionary<string, string[]> termsToCreateDictionary = new Dictionary<string, string[]>();
			if (termsToCreate != null && termsToCreate.Length > 0)
			{
				foreach (string t in termsToCreate) { termsToCreateDictionary.Add(t, null); }
			}
			return CreateTerms(null, parentTerm, termsToCreateDictionary, true);
		}



		///// <summary>
		///// Get Term wssId. Useful for runnung CAML queries
		///// http://pholpar.wordpress.com/2010/02/03/some-words-about-taxonomyfieldvalue-and-its-wssid-property/
		///// </summary>
		///// <param name="rootWeb"></param>
		///// <param name="termId"></param>
		///// <returns>Returns null if not found or error</returns>
		//public static int? GetWssId(SPWeb rootWeb, Guid termId)
		//{
		//    int? wssId = null;
		//    try
		//    {
		//        // init
		//        if (termId == null || termId == Guid.Empty) { throw new Exception("TermId passed is null"); }
		//        if (rootWeb == null) { throw new Exception("SPWeb passed is null"); }
		//        // if (!rootWeb.IsRootWeb) { throw new Exception("SPWeb passed must be a root web!"); }


		//        try
		//        {
		//            if (rootWeb.Properties != null && rootWeb.Properties.ContainsKey("TaxonomyHiddenList"))
		//            {
		//                //Guid taxonomyHiddenListId = new Guid(rootWeb.Properties["TaxonomyHiddenList"]);
		//                //if (taxonomyHiddenListId == null || taxonomyHiddenListId == Guid.Empty) { throw new Exception("Failed to get taxonomyHiddenList Guid"); }
		//                SPList taxonomyHiddenList = rootWeb.Lists.TryGetList("TaxonomyHiddenList");
		//                if (taxonomyHiddenList == null) { throw new Exception("Failed to get taxonomy hidden list: TaxonomyHiddenList"); }
		//                SPQuery query = new SPQuery();
		//                query.Query = String.Format(@"<Where><Eq><FieldRef Name='IdForTerm' /><Value Type='Text'>{0}</Value></Eq></Where>", termId);
		//                SPListItemCollection items = taxonomyHiddenList.GetItems(query);
		//                if (items == null || items.Count < 1) { throw new Exception("query failed to return any items. Query Text: " + query.Query); }
		//                else
		//                {
		//                    if (items.Count > 1) { Log.WriteInformation("query returned " + items.Count + " WssIds for Query Text: " + query.Query); }
		//                    SPField f = items[0].Fields.GetField("ID");
		//                    if (f == null) { throw new Exception("Failed to get ID field for returned hiddenTaxonomy SPListItem"); }
		//                    wssId = Convert.ToInt32(items[0]["ID"].ToString());
		//                }
		//            }
		//        }
		//        catch { }

		//        //if (wssId == null)
		//        //{
		//        //    // failed to get wssId, try Microsoft's own static member
		//        //    int[] wssIds = TaxonomyField.GetWssIdsOfTerm(rootWeb.Site, term.TermStore.Id, term.TermSet.Id, term.Id, false, 1);
		//        //    if (wssIds != null && wssIds.Length == 1) { wssId = wssIds[0]; }
		//        //}
		//    }
		//    catch (Exception ex)
		//    {
		//        Log.WriteError("dsgdgjtsde5rygxdxz", ex);
		//        wssId = null;
		//    }
		//    return wssId;
		//}

		/// <summary>
		/// Get WSS Ids for passed termIds
		///  http://pholpar.wordpress.com/2010/02/03/some-words-about-taxonomyfieldvalue-and-its-wssid-property/
		/// </summary>
		/// <param name="rootWeb"></param>
		/// <param name="termIds"></param>
		/// <returns></returns>
		public static List<int> GetWssIds(SPWeb rootWeb, List<Guid> termIds)
		{
			string camlQuery = null;
			var wssIds = new List<int>();
			try
			{
				// init
				if (rootWeb == null) { var site = Site.GetSite(); rootWeb = (site != null) ? site.RootWeb : null; }
				if (rootWeb == null) { throw new Exception("SPWeb passed is null and SPContext is null"); }
				if (!rootWeb.IsRootWeb) { throw new Exception("Root web passed is NOT a root web"); }
				if (termIds == null) { throw new Exception("TermIds collection passed is null"); }

				if (termIds.Count > 0 && rootWeb.Properties != null && rootWeb.Properties.ContainsKey("TaxonomyHiddenList"))
				{
					// get TaxonomyHiddenList
					SPList taxonomyHiddenList = rootWeb.Lists.TryGetList("TaxonomyHiddenList");
					if (taxonomyHiddenList == null) { throw new Exception("Failed to get taxonomy hidden list: TaxonomyHiddenList"); }

					// create query
					SPQuery q = new SPQuery();
					Caml.FormatSPQueryViewFields(new string[] { "ID" });
					camlQuery = "<Where><In><FieldRef Name='IdForTerm' /><Values>";
					foreach (Guid termId in termIds)
					{
						camlQuery += string.Format("<Value Type='Text'>{0}</Value>", termId);
					}
					camlQuery += "</Values></In></Where>";
					q.Query = camlQuery;

					// fetch items
					SPListItemCollection items = taxonomyHiddenList.GetItems(q);
					if (items == null) { throw new Exception("Failed to get any wssIds from passed termIds"); }

					// convert to wssIds collection
					foreach (SPListItem item in items) { wssIds.Add(item.ID); }

					// DEBUG
					//if (wssIds.Count == 0)
					//{
					//    string termIdsAsString = null; foreach (Guid _termId in termIds) { termIdsAsString += _termId + ", "; }
					//    Log.WriteError("fygubnvfxgdzsfsd", "Failed to get any wssIds using termIds: " + termIdsAsString);
					//}
				}
				else
				{
					throw new Exception("Failed to get reference to TaxonomyHiddenList from SPWeb passed: " + rootWeb.Url);
				}
			}
			catch (Exception ex)
			{
				Log.WriteError("r7ufdhgdsrdssrdsdrt", ex, "CAML: " + camlQuery);
				wssIds = null;
			}
			return wssIds;
		}
		public static List<int> GetWssIds(List<Guid> termIds)
		{
			return GetWssIds(null, termIds);
		}

		public static List<int> GetWssIds(SPWeb rootWeb, TermCollection terms)
		{
			// convert to termsId collection
			var termIds = new List<Guid>();

			if (terms != null && terms.Count > 0)
			{
				foreach (Term term in terms) { termIds.Add(term.Id); }
			}

			// call original
			return GetWssIds(rootWeb, termIds);
		}
		public static List<int> GetWssIds(TermCollection terms)
		{
			return GetWssIds(null, terms);
		}

		/// <summary>
		/// Get wssId for passed termId
		/// </summary>
		/// <param name="rootWeb"></param>
		/// <param name="termId"></param>
		/// <returns></returns>
		public static int? GetWssId(SPWeb rootWeb, Guid termId)
		{
			int? wssId = null;

			// convert into collection
			List<Guid> termIds = new List<Guid>() { termId };

			// call original
			var wssIds = GetWssIds(rootWeb, termIds);

			// get wssId
			if (wssIds != null && wssIds.Count > 0) { wssId = wssIds[0]; }

			return wssId;
		}
		public static int? GetWssId(Guid termId)
		{
			return GetWssId(null, termId);
		}
		/// <summary>
		/// Get wssId from passed term
		/// </summary>
		/// <param name="rootWeb"></param>
		/// <param name="term"></param>
		/// <returns></returns>
		public static int? GetWssId(SPWeb rootWeb, Term term)
		{
			if (term != null) { return GetWssId(rootWeb, term.Id); }
			else { return null; }
		}
		public static int? GetWssId(Term term)
		{
			return GetWssId(null, term);
		}




		/// <summary>
		/// Find terms in existing collection of terms
		/// </summary>
		/// <param name="terms"></param>
		/// <param name="name"></param>
		/// <param name="isSearchLabelsAlso">Also can see if passed name matching an existing term label/alias</param>
		/// <returns></returns>
		public static List<Term> FindTerms(string[] termsToFind, IEnumerable<Term> terms, bool isSearchLabelsAlso)
		{
			List<Term> termsMatched = null;
			try
			{
				// init
				if (terms == null) { throw new Exception("Terms collection passed is null"); }
				if (termsToFind == null) { throw new Exception("No terms to find list passed"); }
				termsMatched = new List<Term>();


				foreach (string termToFind in termsToFind)
				{
					if (!string.IsNullOrEmpty(termToFind))
					{
						string name = FormatTermName(termToFind).ToLower().Trim();
						bool isMatch = false;
						foreach (Term t in terms)
						{
							if (t != null)
							{
								if (t.Name.ToLower().Trim() == name)
								{
									// match
									termsMatched.Add(t);
									isMatch = true;
								}
								else if (isSearchLabelsAlso && t.Labels.Count > 1)
								{
									foreach (Label l in t.Labels)
									{
										if (l.Value.ToLower().Trim() == name)
										{
											// label match
											termsMatched.Add(t);
											isMatch = true;
											break;
										}
									}
								}
								if (isMatch) { break; }
							} // not null Term
						}
					} // not null string termName
				}
			}
			catch (Exception ex)
			{
				Log.WriteError("r6ufgfddfdf", ex);
				termsMatched = null;
			}
			return termsMatched;
		}

		/// <summary>
		/// Find a specific term in the collection of terms passed
		/// </summary>
		/// <param name="terms"></param>
		/// <param name="termToFind"></param>
		/// <param name="isSearchLabelsAlso"></param>
		/// <returns></returns>
		public static Term FindTerm(string termToFind, IEnumerable<Term> terms, bool isSearchLabelsAlso)
		{
			Term term = null;
			try
			{
				// init
				if (terms == null) { throw new Exception("Terms collection passed is null"); }
				if (string.IsNullOrEmpty(termToFind)) { throw new Exception("Term to find is null"); }
				string[] termsToFind = new string[] { termToFind };


				// call original
				List<Term> termsMatched = FindTerms(termsToFind, terms, isSearchLabelsAlso);
				if (termsMatched != null && termsMatched.Count > 0)
				{
					if (termsMatched.Count > 1) { Log.WriteError("trutrdrsgsdr", "term to find: " + termToFind + " returned more than one term. This can happen if collection to search against span multiple term sets w duplicate terms"); }
					term = termsMatched[0];
				}
			}
			catch (Exception ex)
			{
				Log.WriteError("dfutfykuydfdtrdsr", ex, "term to find: " + termToFind);
				term = null;
			}
			return term;
		}

		/// <summary>
		/// Determine if passed term name is valid
		/// </summary>
		/// <param name="termName"></param>
		/// <returns></returns>
		public static bool isValidTermName(string termName)
		{
			bool isValid = false;
			try
			{
				if (string.IsNullOrEmpty(termName)) { throw new Exception("Term Name cannot be null"); }
				if (termName.Length > 255) { throw new Exception("Term name cannot exceede 255 char"); }

				foreach (KeyValuePair<string, string> d in IllegalTermChars)
				{
					if (termName.Contains(d.Key))
					{
						if (d.Key == "  ") { throw new Exception("Term name cannot have a double space"); }
						else { throw new Exception("Term name cannot contain char: " + d.Key); }
					}
				}
				
				// assume success
				isValid = true;
			}
			catch (Exception ex)
			{
				Log.WriteError("srtyfdfdsrdsrsdr", ex, "term name: " + termName);
				isValid = false;
			}
			return isValid;
		}


		/// <summary>
		/// Format term name, making less than 255 char, removing &, etc.
		/// http://sharepointlearningcurve.blogspot.com/2010/04/sp-2010-managed-metadata.html
		/// </summary>
		/// <param name="termName"></param>
		/// <returns></returns>
		public static string FormatTermName(string termName)
		{
			string originalTermName = string.Empty;
			try
			{
				// init
				if (string.IsNullOrEmpty(termName)) { throw new Exception("Term Name passed is null"); }
				originalTermName = termName;


				// trim
				termName = termName.Trim();

				// replace illegal chars
				foreach (KeyValuePair<string, string> d in IllegalTermChars)
				{
					if (termName.Contains(d.Key)) { termName = termName.Replace(d.Key, d.Value); }
				}

				// replace double spaces, just for extra precaution
				termName = termName.Replace("  ", " ").Replace("  ", " ").Trim();

				// decrease to less than 256
				if (termName.Length > 255) { termName = termName.Substring(0, 255); }

				// double check not null
				if (string.IsNullOrEmpty(termName)) { throw new Exception("Failed to format term name, was orignally: " + originalTermName + " but after applying formatting rules, it became null/empty string"); }
			}
			catch (Exception ex)
			{
				Log.WriteError("ryhjdgyfdfssdfdsf", ex, "term name passed: " + originalTermName + " formatted termName: " + termName);
			}
			return termName;
		}

		/// <summary>
		/// formats a term name for html outout, encoding the name
		/// </summary>
		/// <param name="termName"></param>
		/// <returns></returns>
		public static string FormatTermNameForHtml(string termName)
		{
			try
			{
				if (!string.IsNullOrEmpty(termName))
				{
					termName = termName.Replace(AmpersandFullwidth, "&");
					termName = System.Web.HttpUtility.HtmlEncode(termName);
				}
			}
			catch (Exception ex)
			{
				Log.WriteError("r6ti7yjythdgdfddssgxdsdtytuugtfj", ex, "term name: " + termName);
			}
			return termName;
		}

		/// <summary>
		/// Illegal Chars in a Term Name
		/// http://msdn.microsoft.com/en-us/library/ee577519%28v=office.14%29.aspx
		/// </summary>
		public static Dictionary<string, string> IllegalTermChars
		{
			get
			{
				Dictionary<string, string> d = new Dictionary<string, string>();
				d.Add("&", AmpersandFullwidth);
				d.Add("<", "(");
				d.Add(">", ")");
				d.Add("|", "-");
				d.Add(";", "");
				d.Add("\"", "");
				d.Add("\t", " ");
				d.Add("\r", " ");
				d.Add("\n", " ");
				d.Add("  ", " ");
				return d;
			}
		}

		/// <summary>
		/// Sort Terms by their name
		/// </summary>
		/// <param name="terms"></param>
		public static void SortAlphabetically(List<Term> terms)
		{
			try
			{
				if (terms != null && terms.Count > 0)
				{
					terms.Sort(delegate(Term t1, Term t2)
					{
						return t1.Name.CompareTo(t2.Name);
					});
				}
			}
			catch (Exception ex)
			{
				Log.WriteError("fdutgfdfsrd", ex);
			}
		}
		/// <summary>
		/// Sort TaxonomyFieldValue terms
		/// </summary>
		/// <param name="terms"></param>
		public static void SortAlphabetically(List<TaxonomyFieldValue> terms)
		{
			try
			{
				if (terms != null && terms.Count > 0)
				{
					terms.Sort(delegate(TaxonomyFieldValue t1, TaxonomyFieldValue t2)
					{
						return t1.Label.CompareTo(t2.Label);
					});
				}
			}
			catch (Exception ex)
			{
				Log.WriteError("tuddtrursdrtdrt", ex);
			}
		}

		

		///// <summary>
		///// custom sort the descendant terms of passed term
		///// http://aramacciotti.wordpress.com/2011/02/23/customsortorder-in-sharepoint-2010-termsets/
		///// </summary>
		///// <param name="term"></param>
		//public static void CustomSortOrderDescendants(Term term)
		//{
		//    if (term != null) { CustomSortOrderTerms(term.Terms, term.CustomSortOrder, true); }
		//}
		
		/// <summary>
		/// custom sort terms collection
		/// If custom sort order is null, will sort alphabetically
		/// http://aramacciotti.wordpress.com/2011/02/23/customsortorder-in-sharepoint-2010-termsets/
		/// </summary>
		/// <param name="terms"></param>
		/// <param name="customSortOrder"></param>
		public static List<Term> CustomSortOrderTerms(TermCollection termCollection, string customSortOrder)
		{
			var terms = new List<Term>();
			try
			{
				// sort the passed terms collection
				if (termCollection != null && termCollection.Count > 0 && !string.IsNullOrEmpty(customSortOrder))
				{
					string[] termIds = customSortOrder.Split(new char[] {':'}, StringSplitOptions.RemoveEmptyEntries);
					foreach (string id in termIds)
					{
						// foreach term in the collection, find the matching term in the custom sort order string
						foreach (var term in termCollection)
						{
							// found term, now add to collection
							if (term.Id.ToString() == id)
							{
								terms.Add(term);
								break;
							}
						}
					}

					// make sure no terms were missing
					if (termCollection.Count != terms.Count)
					{
						foreach (var term in termCollection)
						{
							// found term not defined in custom sort order, add it to the end of collection
							if (!terms.Contains(term)) { terms.Add(term); }
						}
					}

				} // terms not null or empty and has custom sort defined
				else
				{
					// just convert to collection, no custom sort order
					terms = ConvertTermCollectionToTermsList(termCollection);
					SortAlphabetically(terms);
				}

				// double check my work
				if (termCollection.Count != terms.Count)
				{
					throw new Exception("Failed to order all terms using custom sort order. The termcollection passed has " + termCollection.Count + " terms, but the converted and custom ordered term to return only has " + terms.Count + " terms");
				}
			}
			catch (Exception ex)
			{
				Log.WriteError("tdu6ifufddsds", ex);
			}
			return terms;
		}
		///// <summary>
		///// applies custom sort order to parent terms in term set
		///// </summary>
		///// <param name="termSet"></param>
		//public static List<Term> CustomSortOrderTerms(TermSet termSet)
		//{
		//    var terms = new List<Term>();
		//    try
		//    {
		//        if (termSet != null && termSet.Terms.Count > 0)
		//        {
		//            // call original
		//            terms = CustomSortOrderTerms(termSet.Terms, termSet.CustomSortOrder);
		//        }
		//    }
		//    catch (Exception ex)
		//    {
		//        Log.WriteError("cfxvhjbcgxfdxszddsrz", ex);
		//        terms = null;
		//    }
		//    return terms;
		//}
		

		/// <summary>
		/// Convert a SharePoint specialized terms collection into a Generic list of terms
		/// </summary>
		/// <param name="terms"></param>
		/// <returns></returns>
		public static List<Term> ConvertTermCollectionToTermsList(TermCollection terms)
		{
			List<Term> termsConverted = null;
			try
			{
				// init
				if (terms == null) { throw new Exception("TermsCollection passed is null"); }
				termsConverted = new List<Term>();

				foreach (Term t in terms)
				{
					termsConverted.Add(t);
				}
			}
			catch (Exception ex)
			{
				Log.WriteError("fdutgfdfsrd", ex);
				termsConverted = null;
			}
			return termsConverted;
		}

		/// <summary>
		/// Convert a list of TaxonomyFieldValues (from the Field.GetManagedMetaData) and return termcollection
		/// IMPORTANT! Returns null collection if empty collection passed!
		/// </summary>
		/// <param name="taxonomyFieldValues"></param>
		/// <param name="termStore"></param>
		/// <returns></returns>
		public static TermCollection ConvertTaxonomyFieldValuesToTermCollection(List<TaxonomyFieldValue> taxonomyFieldValues, 
			TermStore termStore)
		{
			TermCollection terms = null;
			try
			{
				// init
				if (taxonomyFieldValues == null) { throw new Exception("TaxonomyFieldValue collection passed is null"); }

				if (taxonomyFieldValues.Count > 0)
				{
					// get term guids
					List<Guid> existingTermGuids = new List<Guid>();
					foreach (TaxonomyFieldValue t in taxonomyFieldValues)
					{
						Guid newTermGuidId = new Guid(t.TermGuid);
						if (newTermGuidId != null && newTermGuidId != Guid.Empty && !existingTermGuids.Contains(newTermGuidId))
						{
							existingTermGuids.Add(newTermGuidId);
						}
					}

					// now get terms
					terms = Taxonomy.GetTerms(existingTermGuids.ToArray(), termStore);
				}
			}
			catch (Exception ex)
			{
				Log.WriteError("zxdfcgjgfdfdyrydtdfcg", ex);
				terms = null;
			}
			return terms;
		}

		public static List<Term> ConvertTaxonomyFieldValuesToTermList(List<TaxonomyFieldValue> taxonomyFieldValues,
			TermStore termStore)
		{
			// convert to termcollection
			var termCollection = ConvertTaxonomyFieldValuesToTermCollection(taxonomyFieldValues, termStore);

			// convert to term list
			return ConvertTermCollectionToTermsList(termCollection);
		}

		/// <summary>
		/// Custom Property names allowed for terms
		/// </summary>
		public enum TermCustomPropertyName
		{
			P3_IsShowInNavigation,
		}

		/// <summary>
		/// Set a custom property to a term (if already exists, will update it)
		/// http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.taxonomy.term.setcustomproperty.aspx
		/// DOES NOT SAVE! To save, use CommitAll();
		/// </summary>
		/// <param name="term"></param>
		/// <param name="name"></param>
		/// <param name="value"></param>
		public static void SetCustomProperty(Term term, string name, string value)
		{
			try
			{
				// init
				if (term == null) { throw new Exception("Term passed is null"); }
				if (string.IsNullOrEmpty(name)) { throw new Exception("Propery name passed is null"); }
				
				
				// validate name
				name = name.Trim();
				if (name.Length > 255) { throw new Exception("Property name cannot exceed 255 char"); }


				// validate value
				if (!string.IsNullOrEmpty(value))
				{
					if (value.Length > 255) { throw new Exception("Property value cannot exceed 255 char"); }
					char[] illegalChars = new char[] { ';', '"', '<', '>', '|', '&', '\t', '\n', '\r' };
					for (int i = 0; i < illegalChars.Length; i++)
					{
						if (value.Contains(illegalChars[i]))
						{
							throw new Exception("Property value has illegal char: " 
								+ illegalChars[i] + " with index: " + i);
						}
					}
				}

				term.SetCustomProperty(name, value);

			}
			catch (Exception ex)
			{
				Log.WriteError("dfgghjgdhfs", ex, "name: " + name + " value: " + value);
			}
		}


		/// <summary>
		/// Is passed term a root term?
		/// </summary>
		/// <param name="term"></param>
		/// <returns></returns>
		public static bool IsRootTerm(Term term)
		{
			if (term.Parent == null) { return true; }
			else { return false; }
		}


		public static List<Term> GetAllChildTerms(Term parentTerm, TermCollection allTermsInTermSet)
		{
			// convert terms collection into generic collection
			List<Term> allTermsInTermSetList = ConvertTermCollectionToTermsList(allTermsInTermSet);

			// call original
			return GetAllChildTerms(parentTerm, allTermsInTermSetList);
		}

		public static List<Term> GetAllChildTerms(Term parentTerm)
		{
			// get all terms
			TermCollection allTermsInTermSet = parentTerm.TermSet.GetAllTerms();

			// convert terms collection into generic collection
			List<Term> allTermsInTermSetList = ConvertTermCollectionToTermsList(allTermsInTermSet);

			// call original
			return GetAllChildTerms(parentTerm, allTermsInTermSetList);
		}

		/// <summary>
		/// Get all child terms of passed parent term
		/// </summary>
		/// <param name="parentTerm"></param>
		/// <param name="allTermsInTermSet"></param>
		/// <returns></returns>
		public static List<Term> GetAllChildTerms(Term parentTerm, List<Term> allTermsInTermSet)
		{
			List<Term> childTerms = null;
			try
			{
				// init
				if (parentTerm == null) { throw new Exception("Parent term passed is null"); }
				if (allTermsInTermSet == null || allTermsInTermSet.Count == 0) { throw new Exception("All terms collection passed is null or empty"); }
				childTerms = new List<Term>();
				int loopCheck = 0;

				// get child terms
				foreach (Term t in allTermsInTermSet)
				{
					if (t.Parent != null && t.Parent.Id == parentTerm.Id)
					{
						childTerms.Add(t);
						getChildTerms(t, allTermsInTermSet, ref childTerms, ref loopCheck);
					}
				}
			}
			catch (Exception ex)
			{
				Log.WriteError("drgggkvjdtsae4te5sysdt", ex);
				childTerms = null;
			}
			return childTerms;
		}

		private static void getChildTerms(Term parentTerm, List<Term> allTermsInTermSet, ref List<Term> terms, ref int loopCheck)
		{
			// infinite loop check
			if (loopCheck > 1000000) { throw new Exception("Infinite Loop Detected Loop check at: " + loopCheck); }
			loopCheck++;

			foreach (Term t in allTermsInTermSet)
			{
				if (t.Parent != null && t.Parent.Id == parentTerm.Id)
				{
					terms.Add(t);
					getChildTerms(t, allTermsInTermSet, ref terms, ref loopCheck);
				}
			}
		}

		/// <summary>
		/// Get all terms in a TermSet, allows for custom sorting
		/// </summary>
		/// <param name="termSet"></param>
		/// <param name="isCustomSort">If true, uses custom sorting as defined in manged term store admin. Otherwise, default alpha sort</param>
		/// <returns></returns>
		public static List<Term> GetAllTerms(TermSet termSet, bool isCustomSort)
		{
			var terms = new List<Term>();
			try
			{
				// init
				if (termSet == null) { throw new Exception("Termset passed is null"); }


				// root terms
				var rootTerms = new List<Term>();
				if (!isCustomSort)
				{
					foreach (Term rootTerm in termSet.Terms) { rootTerms.Add(rootTerm); }
				}
				else
				{
					rootTerms = CustomSortOrderTerms(termSet.Terms, termSet.CustomSortOrder);
				}

				// descendants
				foreach (Term rootTerm in rootTerms)
				{
					// add root term
					terms.Add(rootTerm);
					
					// get all of that term's descendants
					getAllTerms(rootTerm, isCustomSort, ref terms);
				}
			}
			catch (Exception ex)
			{
				Log.WriteError("kkhjhgftyytgtfcxgfcfx", ex);
				terms = null;
			}
			return terms;
		}

		private static void getAllTerms(Term parentTerm, bool isCustomSort, ref List<Term> terms)
		{
			if (parentTerm != null && parentTerm.Terms.Count > 0)
			{
				// get children
				var childTerms = new List<Term>();
				if (!isCustomSort)
				{
					foreach (Term childTerm in parentTerm.Terms) { childTerms.Add(childTerm); }
				}
				else
				{
					childTerms = CustomSortOrderTerms(parentTerm.Terms, parentTerm.CustomSortOrder);
				}

				// get grandkids (recursive)
				foreach (Term childTerm in childTerms)
				{
					// add current child term
					terms.Add(childTerm);

					// add descendants
					getAllTerms(childTerm, isCustomSort, ref terms);
				}
			}
		}

		/// <summary>
		/// find the uppermost parent for a term in passed collection of all terms (for that term set)
		/// </summary>
		/// <param name="term"></param>
		/// <returns></returns>
		public static Term FindUppermostParent(Term term, List<Term> allTerms)
		{
			Term uppermostParentTerm = null;
			try
			{
				// init
				if (term == null) { throw new Exception("Term passed is null"); }
				if (allTerms == null) { throw new Exception("All terms passed is null"); }


				if (term.Parent == null) { uppermostParentTerm = term; }
				else
				{
					int loopCheck = 0;
					Term immediateParent = term.Parent;
					while (uppermostParentTerm == null)
					{
						if (loopCheck > 100000) { throw new Exception("Inifite loop detected, loop exceeded: 100000"); }
						if (immediateParent.Parent == null)
						{
							// found parent uppermost
							uppermostParentTerm = immediateParent;
							break;
						}
						else
						{
							// try again
							immediateParent = immediateParent.Parent;
						}
						loopCheck++;
					}
				}
			}
			catch (Exception ex)
			{
				Log.WriteError("drsgt7isre5assea", ex);
				uppermostParentTerm = null;
			}
			return uppermostParentTerm;
		}


		/// <summary>
		/// Determins which passed term is the lowest in hierarchy
		/// In other words, which term is greater in sepcificity
		/// Root term (least specific) == 0, whereas a child term (more specific) would be lower
		/// If more than one term in the lowest level, then first match returned.
		/// </summary>
		/// <param name="terms"></param>
		/// <param name="termSet"></param>
		/// <returns></returns>
		public static Term FindLowestDescendant(List<Term> terms, TermSet termSet)
		{
			Term lowestTerm = null;
			try
			{
				// init
				if (terms == null || terms.Count == 0) { throw new Exception("Terms collection passed is null or empty"); }
				if (termSet == null) { throw new Exception("TermSet passed is null"); }


				Term lowestTermSoFar = null;
				int lowestLevelSoFar = 0;
				foreach (Term term in terms)
				{
					var level = TermLevel(term.Id, termSet);
					if (level > lowestLevelSoFar)
					{
						// found a new lower term
						lowestTermSoFar = term;
						lowestLevelSoFar = level;
					}
				}

				if (lowestLevelSoFar == 0) { throw new Exception("No terms could be found in passed termset, therefore cannot determine level"); }
				lowestTerm = lowestTermSoFar;
			}
			catch (Exception ex)
			{
				Log.WriteError("ghfhdzsgdgdstdst", ex);
				lowestTerm = null;
			}
			return lowestTerm;
		}


		/// <summary>
		/// Find child terms in passed collection of all terms for a term set
		/// </summary>
		/// <param name="parentTerm">the parent term for which to find child terms</param>
		/// <param name="completeTermHierarchy">The complete collerction of all terms in a term set</param>
		/// <param name="isFindDirectChildrenOnly">If true, only gets those child terms that are direct descendants</param>
		/// <returns></returns>
		public static List<Term> FindChildTerms(Term parentTerm, List<Term> completeTermHierarchy, bool isFindDirectChildrenOnly)
		{
			var childTerms = new List<Term>();
			findChildTerms(parentTerm, completeTermHierarchy, isFindDirectChildrenOnly, ref childTerms);
			return childTerms;
		}
		private static void findChildTerms(Term parentTerm, List<Term> completeTermHierarchy, bool isFindDirectChildrenOnly, ref List<Term> childTerms)
		{
			string currentTerm = null;
			try
			{
				if (parentTerm != null && completeTermHierarchy != null && completeTermHierarchy.Count > 0)
				{
					foreach (Term t in completeTermHierarchy)
					{
						currentTerm = t.Name;
						if (t.Parent != null && t.Parent.Id == parentTerm.Id)
						{
							// found child term, add to collection
							if (!childTerms.Contains(t)) { childTerms.Add(t); }

							// if look for all descendants, call self
							if (!isFindDirectChildrenOnly)
							{
								findChildTerms(t, completeTermHierarchy, isFindDirectChildrenOnly, ref childTerms);
							}
						} // match found
					} // for each term in term set
				}
			}
			catch (Exception ex)
			{
				Log.WriteError("tu7yfufddfsd", ex, "error on term: " + currentTerm);
			}
		}
		/// <summary>
		/// find all parent terms, walks up the tree, starting with the term passed
		/// (Does not include passed term, if no parent, returns empty collection)
		/// </summary>
		/// <param name="term"></param>
		/// <param name="isOrderByUppermostParentFirst">If true, the returned collection will have the uppermost (root) term first</param>
		/// <returns></returns>
		public static List<Term> FindParentTerms(Term term, bool isOrderByUppermostParentFirst)
		{
			var parentTerms = new List<Term>();
			findParentTerms(term, ref parentTerms);

			// by default, collection is by child first, then parent, then grand parent etc.
			// if parent uppermost first, then reverses this
			if (isOrderByUppermostParentFirst && parentTerms != null && parentTerms.Count > 0)
			{
				parentTerms.Reverse();
			}
			return parentTerms;
		}
		private static void findParentTerms(Term term, ref List<Term> parentTerms)
		{
			if (term != null && term.Parent != null)
			{
				parentTerms.Add(term.Parent);
				findParentTerms(term.Parent, ref parentTerms);
			}
		}

		/// <summary>
		/// Determin if passed child term is a descendant of passed parent term. 
		/// (direct or indirect descendant)
		/// </summary>
		/// <param name="childTerm"></param>
		/// <param name="parentTerm"></param>
		/// <param name="completeTermHierarchy"></param>
		/// <returns></returns>
		public static bool IsDescendant(Term childTerm, Term parentTerm, List<Term> completeTermHierarchy)
		{
			bool isDecendant = false;
			try
			{
				if (childTerm != null && parentTerm != null && completeTermHierarchy != null && completeTermHierarchy.Count > 0)
				{
					// find all child terms for passed parent term
					List<Term> childTerms = FindChildTerms(parentTerm, completeTermHierarchy, false);
					if (childTerms != null && childTerms.Count > 0)
					{
						foreach (Term t in childTerms)
						{
							if (t.Id == childTerm.Id) { isDecendant = true; break; }
						}
					}
				}
			}
			catch (Exception ex)
			{
				Log.WriteError("yittf76dr5eesrse", ex);
			}
			return isDecendant;
		}

		/// <summary>
		/// Determine if two terms passed are siblings (have same parent)
		/// </summary>
		/// <param name="term"></param>
		/// <param name="siblingTerm"></param>
		/// <param name="completeTermHierarchy"></param>
		/// <returns></returns>
		public static bool IsSibling(Term term, Term siblingTerm, List<Term> completeTermHierarchy)
		{
			bool isSibling = false;
			try
			{
				if (term != null && siblingTerm != null && completeTermHierarchy != null && completeTermHierarchy.Count > 0)
				{
					if (term.Parent == null && siblingTerm.Parent == null)
					{
						// both terms are root terms
						isSibling = true;
					}
					else if (term.Parent != null && siblingTerm.Parent != null && term.Parent.Id == siblingTerm.Parent.Id)
					{
						isSibling = true;
					}
				}
			}
			catch (Exception ex)
			{
				Log.WriteError("drgdffyfdfdxfdfgtTT", ex);
			}
			return isSibling;
		}


		/// <summary>
		/// Determine level that term is at inside termset
		/// 0 = error, 1 = root term, 2 = child of root term, etc...
		/// At least one term passed must be root term, or returns 0
		/// </summary>
		/// <param name="term"></param>
		/// <param name="termList"></param>
		/// <returns></returns>
		public static int TermLevel(Term term, List<Term> termsList)
		{
			int level = 1;
			bool isMatch = false;
			try
			{
				// init
				if (term == null) { throw new Exception("Term passed is null"); }
				if (termsList == null) { throw new Exception("Terms collection passed is null"); }
				if (termsList.Count == 0) { throw new Exception("Terms collection passed is empty"); }


				// get level by parent
				if (term.Parent == null) { level = 1; isMatch = true; }
				else if (term.Parent.Parent == null) { level = 2; isMatch = true; }
				else if (term.Parent.Parent.Parent == null) { level = 3; isMatch = true; }
				else if (term.Parent.Parent.Parent.Parent == null) { level = 4; isMatch = true; }
				else if (term.Parent.Parent.Parent.Parent.Parent == null) { level = 5; isMatch = true; }

				else
				{
					// more than 5 levels deep, recursively lookup level

					// get root terms
					var rootTerms = new List<Term>();
					foreach (var rootTerm in termsList) { if (rootTerm.Parent == null) { rootTerms.Add(rootTerm); } }
					if (rootTerms.Count == 0) { throw new Exception("No root terms were passed"); }

					// loop through root terms, walking down tree until match found
					foreach (Term rootTerm in termsList)
					{
						if (rootTerm.Id == term.Id)
						{
							isMatch = true;
							break;
						}
						else if (rootTerm.Terms.Count > 0)
						{
							// root term has child terms, recursive lookup
							if (isTermMatch(term.Id, rootTerm.Terms, true, ref level))
							{
								isMatch = true;
								break;
							}
						}
					}
				}

				// no match, error
				if (!isMatch) { level = 0; }
			}
			catch (Exception ex)
			{
				string termAsString = (term != null) ? term.Name : null;
				Log.WriteError("xfghjvkgcfxhgfgxfgd", ex, "term: " + termAsString);
				level = 0;
			}
			return level;
		}

		/// <summary>
		/// Determine level that term is at inside termset
		/// 0 = error, 1 = root term, 2 = child of root term, etc...
		/// </summary>
		/// <param name="termId"></param>
		/// <param name="terms"></param>
		/// <returns></returns>
		public static int TermLevel(Guid termId, List<Term> termsList)
		{
			if (termId != null && termId != Guid.Empty && termsList != null && termsList.Count > 0)
			{
				// find term from passed termId
				Term term = null;
				foreach (var t in termsList) { if (t.Id == termId) { term = t; break; } }

				// call original
				if (term != null) { return TermLevel(term, termsList); }
			}
			return 0;
		}

		/// <summary>
		/// Determine level that term is at inside termset
		/// 0 = error, 1 = root term, 2 = child of root term, etc...
		/// </summary>
		/// <param name="termId"></param>
		/// <param name="terms"></param>
		/// <returns></returns>
		public static int TermLevel(Guid termId, TermCollection termsCollection)
		{
			return TermLevel(termId, ConvertTermCollectionToTermsList(termsCollection));
		}

		/// <summary>
		/// Determine level that term is at inside termset
		/// 0 = error, 1 = root term, 2 = child of root term, etc...
		/// </summary>
		/// <param name="termId"></param>
		/// <param name="terms"></param>
		/// <returns></returns>
		public static int TermLevel(Term term, TermCollection termsCollection)
		{
			return TermLevel(term, ConvertTermCollectionToTermsList(termsCollection));
		}

		/// <summary>
		/// Determine level that term is at inside termset
		/// 0 = error, 1 = root term, 2 = child of root term, etc...
		/// </summary>
		/// <param name="termId"></param>
		/// <param name="termSet"></param>
		/// <returns></returns>
		public static int TermLevel(Guid termId, TermSet termSet)
		{
			if (termSet != null) { return TermLevel(termId, termSet.GetAllTerms()); }
			else 
			{
				Log.WriteError("435yi65y4srehrsesgdfdxfszdxferw", "TermSet passed is null, cannot determine term level");
				return 0; 
			}
		}

		/// <summary>
		/// Finds term in passed terms and their desendants
		/// </summary>
		/// <param name="termToMatchId"></param>
		/// <param name="termsToLookIn"></param>
		/// <param name="isSearchDescendants"></param>
		/// <returns></returns>
		private static bool isTermMatch(Guid termToMatchId, TermCollection termsToLookIn, bool isSearchDescendants,
			ref int level)
		{
			bool isMatch = false;
			int currentLevel = level + 1;
			foreach (Term childTerm in termsToLookIn)
			{
				if (childTerm.Id == termToMatchId)
				{
					// found term
					isMatch = true;
					break;
				}
				else if (isSearchDescendants && childTerm.Terms.Count > 0)
				{
					// has child terms, recursive lookup
					if (isTermMatch(termToMatchId, childTerm.Terms, isSearchDescendants, ref currentLevel))
					{
						// match found in descendant
						isMatch = true;
						break;
					}
				}
			}

			// set level if match found
			if (isMatch) { level = currentLevel; }

			return isMatch;
		}

		/// <summary>
		/// Convert TermGuid string in TaxonomyFieldValue collection into Guid collection
		/// </summary>
		/// <param name="taxonomyFieldValues"></param>
		/// <returns></returns>
		public static List<Guid> ConvertTaxonomyFieldValuesIdsToGuids(List<TaxonomyFieldValue> taxonomyFieldValues)
		{
			var termIds = new List<Guid>();
			if (taxonomyFieldValues != null && taxonomyFieldValues.Count > 0)
			{
				foreach (TaxonomyFieldValue t in taxonomyFieldValues)
				{
					termIds.Add(new Guid(t.TermGuid));
				}
			}
			return termIds;
		}
		/// <summary>
		/// Convert TermGuid string in TaxonomyFieldValue collection into Guid collection
		/// </summary>
		/// <param name="taxonomyFieldValues"></param>
		/// <returns></returns>
		public static List<Guid> ConvertTaxonomyFieldValuesIdsToGuids(TaxonomyFieldValueCollection taxonomyFieldValues)
		{
			var termIds = new List<Guid>();
			if (taxonomyFieldValues != null && taxonomyFieldValues.Count > 0)
			{
				foreach (TaxonomyFieldValue t in taxonomyFieldValues)
				{
					termIds.Add(new Guid(t.TermGuid));
				}
			}
			return termIds;
		}
		

		


	} // txonomy class
}
